package com.asen.libcommon.exception

import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.os.Process
import com.asen.libcommon.util.DateUtil
import com.orhanobut.logger.Logger
import java.io.*


/**
 * @author : asenLiang
 * @date   : 2021/03/03
 * @e-mail : liangAisiSen@163.com
 * @desc   : 异常打印类
 */
open class CrashHandler(
    // TODO：系统默认的异常处理（默认情况下，系统会终止当前的异常程序）
    private var mDefaultCrashHandler: Thread.UncaughtExceptionHandler? = null,
    private var mContext: Context? = null
) : Thread.UncaughtExceptionHandler {

    companion object {

        /** 文件开头名字 */
        private const val FILE_NAME = "err"

        /** 异常文件夹 */
        protected var mFilePath: String? = null

        /** log文件的后缀名 */
        private const val FILE_NAME_SUFFIX = ".txt"

        /** 获取SD卡错误日志存储路径 */
        private fun getSDCardErrorLogFilePath() = "${getSDCardFilePath()}/ErrorLogs"

        /** 获取 SD 卡存储文件路径 */
        private fun getSDCardFilePath() = "${Environment.getExternalStorageDirectory()}/$mFilePath"

    }

    fun init(context: Context, filePath: String) {
        mFilePath = filePath
        // TODO：获取系统默认的异常处理器
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
        // TODO：将当前实例设为系统默认的异常处理器
        Thread.setDefaultUncaughtExceptionHandler(this)
        // TODO：获取Context，方便内部使用
        mContext = context.applicationContext
    }

    /**
     * 这个是最关键的函数，当程序中有未被捕获的异常，系统将会自动调用#uncaughtException方法
     * thread为出现未捕获异常的线程，ex为未捕获的异常，有了这个ex，我们就可以得到异常信息。
     */
    override fun uncaughtException(thread: Thread, ex: Throwable) {
        try {
            // TODO：导出异常信息到SD卡中
            dumpExceptionToSDCard(ex)
            // TODO：这里可以通过网络上传异常信息到服务器，便于开发人员分析日志从而解决bug
            uploadExceptionToServer()
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // TODO：打印出当前调用栈信息
        ex.printStackTrace()

        // TODO：如果系统提供了默认的异常处理器，则交给系统去结束我们的程序，否则就由我们自己结束自己
        if (mDefaultCrashHandler != null) {
            mDefaultCrashHandler?.uncaughtException(thread, ex)
        } else {
            Process.killProcess(Process.myPid())
        }
    }

    @Throws(IOException::class)
    private fun dumpExceptionToSDCard(ex: Throwable) {
        // TODO：如果SD卡不存在或无法使用，则无法把异常信息写入SD卡
        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) return

        val dir = File(getSDCardErrorLogFilePath())

        if (!dir.exists()) dir.mkdirs()

        val time = DateUtil.parseTimeYYYYMMDDHHMMSS(System.currentTimeMillis()) // yyyy-MM-dd'T'HHmmss

        // TODO：以当前时间创建log文件
        val file = File(getSDCardErrorLogFilePath() + "/" + FILE_NAME + time + FILE_NAME_SUFFIX)

        try {
            val pw = PrintWriter(BufferedWriter(FileWriter(file)))
            // TODO：导出发生异常的时间
            pw.println(time)
            // TODO：导出手机信息
            dumpPhoneInfo(pw)
            pw.println()
            // TODO：导出异常的调用栈信息
            ex.printStackTrace(pw)
            pw.close()
        } catch (e: Exception) {
            Logger.e("dump crash info failed")
        }
    }

    @Throws(PackageManager.NameNotFoundException::class)
    private fun dumpPhoneInfo(pw: PrintWriter) {
        // 应用的版本名称和版本号
        val pm = mContext!!.packageManager
        val pi = pm.getPackageInfo(mContext!!.packageName, PackageManager.GET_ACTIVITIES)

        // TODO：app版本号
        pw.print("App Version: ")
        pw.print(pi.versionName)
        pw.print('_')
        pw.println(pi.versionCode)

        // TODO：android版本号
        pw.print("OS Version: ")
        pw.print(Build.VERSION.RELEASE)
        pw.print("_")
        pw.println(Build.VERSION.SDK_INT)

        // TODO：手机制造商
        pw.print("Vendor: ")
        pw.println(Build.MANUFACTURER)

        // TODO：手机型号
        pw.print("Model: ")
        pw.println(Build.MODEL)

        // TODO：cpu架构
        pw.print("CPU ABI: ")
        pw.println(Build.CPU_ABI)
    }

    /** 上传错误信息到服务器 */
    private fun uploadExceptionToServer() {
        // TODO 上传全局捕抓错误信息到服务器
    }

}